// 数据结构bucket
let bucket = new WeakMap<Object, Map<Object, Set<IWrapper | null>>>();
const acctiveFnStack: Array<IWrapper | null> = [];
const Iterate_Key = Symbol();
enum Type {
  Add,
  Update,
}
interface IWrapper<T = any> {
  (): T;
  deps: Array<Set<Function | null>>;
  options?: IOptions;
}
let activeEffectFn: IWrapper | null = null;
interface Iobj {
  name: string;
  age: number;
  ok: boolean;
  num: number;
  bar: any;
  ccc?: string;
}

interface IOptions {
  schedule: (effect: Function) => any;
  lazy?: boolean;
}

function track<T = any>(target: T, key: string | symbol) {
  if (!activeEffectFn) return;
  // 需要将target对应的key添加到桶中
  let map = bucket.get(target);
  if (!map) {
    bucket.set(target, (map = new Map()));
  }
  let set = map.get(key);
  if (!set) {
    map.set(key, (set = new Set()));
  }
  set.add(activeEffectFn);
  activeEffectFn.deps.push(set);
}
function trigger<T = any>(target: T, key: string | symbol, type: Type) {
  const map = bucket.get(target);
  if (!map) return;
  const effectFns = map.get(key);
  const efectsToRun = new Set<IWrapper | null>();
  const iterate_effectfns = map.get(Iterate_Key);
  effectFns &&
    effectFns.forEach((effect) => {
      efectsToRun.add(effect);
    });
  if (type === Type.Add) {
    iterate_effectfns &&
      iterate_effectfns.forEach((effect) => {
        efectsToRun.add(effect);
      });
  }

  efectsToRun.forEach((effect) => {
    if (effect.options && effect.options.schedule) {
      effect.options.schedule(effect);
      return;
    }
    if (effect && effect !== activeEffectFn) effect();
  });
}
function cleanUp(wrapper: IWrapper) {
  const deps: Array<Set<Function | null>> = wrapper.deps;
  for (let i = 0; i < deps.length; i++) {
    const set = deps[i];
    set.delete(wrapper);
  }
  wrapper.deps.length = 0;
}
function effect<T>(fn: () => T, options?: IOptions): any {
  const wrapper: IWrapper = function () {
    cleanUp(wrapper);
    acctiveFnStack.push(wrapper);
    activeEffectFn = wrapper;
    let value = fn();
    acctiveFnStack.pop();
    activeEffectFn = acctiveFnStack[acctiveFnStack.length - 1];
    return value;
  };
  // 在触发之前清除掉后续没有绑定的属性
  wrapper.deps = [];
  wrapper.options = options;
  if (options && options.lazy) {
    return wrapper;
  } else {
    return wrapper();
  }
}
let isFlush = false;
let jobQueue = new Set<IWrapper>();
let p = Promise.resolve();
function jobRun() {
  if (isFlush) return;
  isFlush = true;
  p.then(() => {
    jobQueue.forEach((fn) => {
      fn();
    });
  }).finally(() => {
    isFlush = false;
  });
}
// set的时候触发trigger
let handle: ProxyHandler<Iobj> = {
  get(target, key, receiver) {
    // 当get的时候触发track
    track(target, key);
    return Reflect.get(target, key as keyof Iobj, receiver);
  },
  set(target, key: any, newValue, receiver) {
    const type = Object.prototype.hasOwnProperty.call(target, key)
      ? Type.Update
      : Type.Add;

    Reflect.set(target, key as keyof Iobj, newValue, receiver);
    trigger(target, key, type);
    return true;
  },
  has(target, key) {
    track(target, key);
    return Reflect.has(target, key as keyof Iobj);
  },
  ownKeys(target) {
    track(target, Iterate_Key);
    return Reflect.ownKeys(target);
  },
};
const obj: Iobj = {
  name: "zs",
  age: 14,
  ok: true,
  num: 1,
  get bar() {
    return this.age;
  },
};
// proxy代理对象
let proxyObj = new Proxy(obj, handle);
effect(() => {
  for (const key in proxyObj) {
    console.log(key);
  }
});
proxyObj.ccc = "update";
// computed的实现
function computed<T>(getter: () => T) {
  let isCache = false;
  let effectFn = effect<T>(getter, {
    schedule: function (effect: IWrapper<T>) {
      effectFn = effect;
      isCache = false;
      trigger(obj, "value", Type.Update);
    },
    lazy: true,
  });
  let value = effectFn();
  const obj = {
    get value() {
      if (!isCache) {
        value = effectFn();
        isCache = true;
        track(obj, "value");
      }
      return value;
    },
  };
  return obj;
}
//watch的实现
/**
 * 1 watcher传入一个函数或者属性或属性字符串  一个回调
 * 2 如果函数直接放到effect中
 *    如果是
 *
 */
function travese(obj: { [key: string]: any }, map = new Map()) {
  const target: { [key: string]: any } = {};
  map.set(obj, obj);
  for (const key in obj) {
    if (typeof obj[key] === "object" && !map.has(obj[key])) {
      target[key] = travese(obj[key], map);
    } else {
      target[key] = obj[key];
    }
  }
  return { ...target };
}
interface IWatcherOptions {
  immidate: boolean;
}

function watch<T>(
  source: () => T,
  cb: (
    oldValue: T,
    newValue: T,
    changeExpire?: (fn: () => void) => void
  ) => void,
  options?: IWatcherOptions
): void;

function watch<T extends object>(
  source: T,
  cb: (oldValue: T, newValue: T) => void,
  options?: IWatcherOptions
): void;

function watch<T>(
  source: () => T,
  cb: (
    oldValue: T,
    newValue: T,
    changeExpire?: (fn: () => void) => void
  ) => void,
  options: IWatcherOptions
) {
  let getter: () => any;
  let oldValue: any;
  let newValue: any;
  let cleanUp: () => void;
  let overwriteExpire: (fn: () => void) => void;
  overwriteExpire = (fn: () => void) => {
    cleanUp = fn;
  };
  if (typeof source === "function") {
    getter = () => source();
  } else if (typeof source === "object") {
    getter = () => travese(source);
  } else {
    return;
  }
  function job() {
    if (cleanUp) {
      cleanUp();
    }
    newValue = curEffectFn();
    cb(oldValue, newValue, overwriteExpire);
    oldValue = newValue;
  }
  let curEffectFn = effect(getter, {
    schedule: function (effectFn: IWrapper) {
      curEffectFn = effectFn;
      job();
    },
    lazy: true,
  });
  if (options?.immidate) {
    job();
  } else {
    curEffectFn();
  }
}
